package org.example.aop.aspect;

import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.example.aop.annotation.AnnotationResolver;
import org.example.aop.annotation.DistributedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.concurrent.TimeUnit;

/**
 * @author Zhou ShiYang
 * @date 2022/5/13 11:25
 */
@Aspect
@Component
@Slf4j
public class DistributedLockAspect {

    @Autowired
    private RedissonClient redissonClient;


    @Around(value = "@annotation(distributedLock)", argNames = "joinPoint,distributedLock")
    public Object doAround(ProceedingJoinPoint joinPoint, DistributedLock distributedLock) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        String lockKey = (String) AnnotationResolver.newInstance().resolver(joinPoint, distributedLock.lockKey());
        long lockTime = distributedLock.lockTime();
        long tryLockWaitTime = distributedLock.tryLockWaitTime();
        boolean needTryLock = distributedLock.needTryLock();
        log.info("lockKey >>>>>>> {}", lockKey);
        RLock lock = redissonClient.getLock(lockKey);

        try {
            // 第一次
            if (getLock(lock, lockTime, tryLockWaitTime)) {
                log.info("first get lock >>>>>>> thread:{}, lock:{}", Thread.currentThread().getId(), lock);
                return joinPoint.proceed();
            }
            Preconditions.checkState(needTryLock, distributedLock.error());
            while (true) {
                if (getLock(lock, lockTime, tryLockWaitTime)) {
                    log.info("try get lock break cycle >>>>>>> thread:{}, lock:{}", Thread.currentThread().getId(), lock);
                    break;
                }
            }
            log.info("try get lock >>>>>>> thread:{}, lock:{}", Thread.currentThread().getId(), lock);
            return joinPoint.proceed();
        } finally {
            // 是否还是锁定状态
            log.info("thread status >>>>>>> thread:{},lock:{},locked:{}", Thread.currentThread().getId(), lock, lock.isLocked());
            if (lock.isLocked()) {
                // 是否是当前执行线程的锁
                if (lock.isHeldByCurrentThread()) {
                    log.info("thread unlock >>>>>>> thread:{}, lock:{}", Thread.currentThread().getId(), lock);
                    // 释放锁
                    lock.unlock();
                }
            }
        }
    }

    private boolean getLock(RLock lock, long lockTime, long tryLockWaitTime) {

        try {
            // leaseTime: 锁有效时间 防止锁一直被占用
            // waitTime: 等待时间 没有获取到锁继续等待的时间
            if (lockTime == -1L) {
                return lock.tryLock(tryLockWaitTime, TimeUnit.SECONDS);
            } else {
                return lock.tryLock(tryLockWaitTime, lockTime, TimeUnit.SECONDS);
            }

        } catch (InterruptedException e) {
            return false;
        }
    }

}
